filajs.server = 'http://filajs.net/';

filajs.cardFamily = 'classic';
filajs.suitFamily = 'silk';

// Η property "cardWidth" δείχνει το default πλάτος των παιγνιοχάρτων σε
// pixels. Ουσιαστικά χρησιμοποιείται ως συντελεστής αναλογίας.

filajs.cardWidth = 88.0;

// Η property "cardRatio" δείχνει την αναλογία ύψους/πλάτους των διαστάσεων
// των παιγνιοχάρτων.

filajs.cardRatio = 120.0 / 88.0;

// Η property "cardRaise" δείχνει πόσα pixels θα ανεβαίνει ένα φύλλο με τη
// μέθοδο raise. Η μετατόπιση προσαρμόζεται ανάλογα με τις διαστάσεις του
// παιγνιοχάρτου.

filajs.cardRaise = 12.0;

// Οι properties "asortiRatioH/V" δείχνουν το πλάτος του διαχωριστικού
// όμορων ομοιόχρωμων φυλών, ως ποσοστό του πλάτους ή του ύψους των
// παιγνιοχάρτων για οριζόντιες ή κάθετες χαρτωσιές αντίστοιχα.

filajs.asortiRatioH = 0.08;
filajs.asortiRatioV = 0.06;
filajs.asortiColor = 'rgba(60, 254, 225, 0.3)';

filajs.shiftx = 0.30;
filajs.shifty = 0.45;

filajs.serverSet = function(server) {
	filajs.server = server;
	return filajs;
};

filajs.serverGet = function() {
	return filajs.server;
};

filajs.cardFamilySet = function(family) {
	filajs.cardFamily = family;
	return filajs;
};

filajs.cardFamilyGet = function() {
	return filajs.cardFamily;
};

filajs.suitFamilySet = function(family) {
	filajs.suitFamily = family;
	return filajs;
};

filajs.suitFamilyGet = function() {
	return filajs.suitFamily;
};

filajs.suitDOM = function(suit, size, family) {
	if (size === undefined)
	size = 30;

	if (family === undefined)
	family = filajs.suitFamilyGet();

	return $('<img>').addClass('filajsSuit').
	attr('src', filajs.serverGet() + 'images/suits/' + family + '/' + suit + '.png').
	css('width', size + 'px');
};

String.prototype.filajsSuitDOM = function(size, family) {
	return filajs.suitDOM(this.valueOf(), size, family);
};

filajs.cardWidthSet = function(width) {
	filajs.cardWidth = width;
	return filajs;
};

filajs.cardWidthGet = function() {
	return filajs.cardWidth;
};

filajs.shiftxSet = function(dx) {
	filajs.shiftx = dx;
	return filajs;
};

filajs.shiftxGet = function() {
	return filajs.shiftx;
};

filajs.shiftySet = function(dy) {
	filajs.shifty = dy;
	return filajs;
};

filajs.shiftyGet = function() {
	return filajs.shifty;
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η κλάση "filajsCard" εμπλουτίζεται με κάποιες properties που αφορούν στα
// DOM elements των σχετικών αντικειμένων. Πιο συγκεκριμένα εισάγονται οι
// παρακάτω properties:
//
//	dom		Πρόκειται για το jQuery DOM element του ανά χείρας
//			φύλλου.
//
//	width		Το πλάτος του DOM element του ανά χείρας φύλλου, σε
//			pixels.
//
//	family		Είναι η οικογένεια παιγνιοχάρτων που χρησιμοποιείται
//			για τον σχηματισμό της εικόνας του ανά χείρας φύλλου.

filajsCard.prototype.widthSet = function(width) {
	if (width === undefined)
	width = filajs.cardsWidth;

	else if ((typeof width !== 'number') || (width < 0))
	throw filajs.project + ': ' + width + ': invalid card width';

	this.width = width;
	return this;
};

filajsCard.prototype.widthGet = function() {
	return this.width;
};

filajsCard.prototype.familySet = function(family) {
	if (family === undefined)
	family = filajs.cardFamily;

	else if (typeof family !== 'string')
	throw filajs.project + ': ' + family + ': invalid card family';

	this.family = family;
	return this;
};

filajsCard.prototype.familyGet = function() {
	return this.family;
};

filajsCard.prototype.domCreate = function() {
	if (this.hasOwnProperty('dom'))
	throw filajs.project + ': dom already exists';

	this.dom = $('<div>').
	data('card', this).
	addClass('filajsCard');

	this.dom.
	append($('<div>').addClass('filajsCardText filajsCardTextTL')).
	append($('<div>').addClass('filajsCardText filajsCardTextBR')).
	append($('<img>').addClass('filajsCardImage'));

	if (!this.hasOwnProperty('width'))
	this.widthSet(filajs.cardWidth);

	if (!this.hasOwnProperty('family'))
	this.familySet(filajs.cardFamily);

	return this;
};

filajsCard.prototype.domRefresh = function() {
	this.
	domRefreshImage().
	domRefreshWidth();
	return this;
};

filajsCard.prototype.domRefreshImage = function() {
	var dom, family, s;

	dom = this.domGet();
	family = this.familyGet();
	s = this.toString(true);

	dom.
	removeClass().
	addClass('filajsCard filajsCard_' + family);

	dom.
	children('.filajsCardImage').
	attr('src', filajs.serverGet() + 'images/cards/' + family + '/' + s + '.png');

	dom.
	children('.filajsCardText').
	html(s.substr(0, 1) + '<br>' + s.substr(1, 1));

	return this;
};

filajsCard.prototype.domRefreshWidth = function() {
	var width, w;

	width = this.widthGet();
	w = width * (1.0 / 88.0);

	this.domGet().
	css({
		width: width + 'px',
		height: (width * filajs.cardRatio) + 'px',
		boxShadow: (-2 * w) + 'px ' + (-1 * w) + 'px ' + (4 * w) + 'px rgba(23, 31, 21, 0.40),' +
			(0.5 * w) + 'px ' + (0.5 * w) + 'px ' + (1 * w) + 'px rgba(23, 31, 21, 0.40)',
	}).
	children('.filajsCardText').
	css({
		fontSize: (18 * w) + 'px',
		padding: (4 * w) + 'px',
	});

	return this;
};

filajsCard.prototype.domGet = function() {
	return this.dom;
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η κλάση "filajsHand" εμπλουτίζεται με κάποιες properties που αφορούν στα
// DOM elements των σχετικών αντικειμένων. Πιο συγκεκριμένα εισάγονται οι
// παρακάτω properties:
//
//	dom		Πρόκειται για το jQuery DOM element της ανά χείρας
//			χαρτωσιάς.
//
//	horizontal	Είναι 0, 1, ή -1 και εφόσον είναι διάφορη του μηδενός
//			σημαίνει ότι η χαρτωσιά εκτείνεται οριζόντια. Το πρόσημο
//			δείχνει προς ποια κατεύθυνση μετατοπίζονται τα φύλλα της
//			χαρτωσιάς με την μέθοδο raise. Πιο συγκεκριμένα, έχουμε
//			μετατόπιση προς τα πάνω αν έχουμε θετικό πρόσημο, και
//			μετατόπιση προς τα κάτω αν το πρόσημο είναι αρνητικό.
//
//	vertical	Είναι 0, 1, ή -1 και εφόσον είναι διάφορη του μηδενός
//			σημαίνει ότι η χαρτωσιά εκτείνεται κάθετα. Το πρόσημο
//			δείχνει προς ποια κατεύθυνση μετατοπίζονται τα φύλλα της
//			χαρτωσιάς με την μέθοδο raise. Πιο συγκεκριμένα, έχουμε
//			μετατόπιση προς τα δεξιά αν έχουμε θετικό πρόσημο, και
//			μετατόπιση προς τα αριστερά αν το πρόσημο είναι αρνητικό.
//
//	shiftx		Είναι ένας δεκαδικός αριθμός που δείχνει την αναλογία
//			του «απανωτίσματος» των φύλλων κατά πλάτος σε σχέση με
//			το πλάτος των φύλλων της ανά χείρας χαρτωσιάς.
//
//	shifty		Είναι ένας δεκαδικός αριθμός που δείχνει την αναλογία
//			του «απανωτίσματος» των φύλλων καθ' ύψος σε σχέση με
//			το ύψος φύλλων της ανά χείρας χαρτωσιάς.
//
//	baseline	Δείχνει το πώς τοποθετείται η χαρτωσιά στο πατρικό DOM
//			element ως εξής:
//
//				Horizontal	B	Bottom based
//						T	Top hanged
//
//				Vertical	L	Left snapped
//						R	Right snapped
//
//	baselineOffset	Μετατόπιση από το baseline σε pixels.
//
//	alignment	Δείχνει τη θέση της χαρτωσιάς στο πατρικό DOM element:
//
//				Horizontal	L	Left aligned
//						R	Right aligned
//						C	Centered
//
//				Vertical	T	Top hanged
//						B	Bottom based
//						M	Middled
//
//	circle		Array από DOM elements τα οποία καθορίζουν τις σχετικές
//			θέσεις στις οποίες θα τοποθετηθούν κυκλικά τα φύλλα της
//			χαρτωσιάς με absolute positioning. Ο καθορισμός θέσης
//			γίνεται με βάση το πατρικό div. Η παραγόμενη χαρτωσιά
//			υπόκειται σε τοποθέτηση μέσω baseline και alignment, ενώ
//			μπορούμε να αυξομειώσουμε και το μέγεθος της χαρτωσιάς
//			με βάση το μέγεθος των φύλλων σε σχέση με το μέγεθος
//			της πρώτης θέσης.
//
//	asorti		Η property "asorti" δείχνει αν θέλουμε διαχωρισμό με
//			χρωματισμένη σκιά σε ομοιόχρωμα φύλλα. Αν, δηλαδή,
//			γειτονεύουν μπαστούνια με σπαθιά, ή καρά με κούπες, τότε
//			παρεμβάλλεται χρωματισμένη σκίαση ώστε να γίνεται εμφανής
//			η αλλαγή της φυλής.

filajsHand.prototype.horizontalSet = function(h) {
	if (h === undefined)
	this.horizontal = 1;

	else if (typeof h !== 'number')
	throw filajs.project + ': ' + h + ': invalid horizontal setting';

	else if (h > 0)
	this.horizontal = 1;

	else if (h < 0)
	this.horizontal = -1;

	else
	this.horizontal = 0;

	if (this.horizontal)
	this.verticalSet(0);

	return this;
};

filajsHand.prototype.verticalSet = function(v) {
	if (v === undefined)
	this.vertical = 1;

	else if (typeof v !== 'number')
	throw filajs.project + ': ' + v + ': invalid vertical setting';

	else if (v > 0)
	this.vertical = 1;

	else if (v < 0)
	this.vertical = -1;

	else
	this.vertical = 0;

	if (this.vertical)
	this.horizontalSet(0);

	return this;
};

filajsHand.prototype.shiftxSet = function(shiftx) {
	this.shiftx = shiftx;
	return this;
};

filajsHand.prototype.shiftxGet = function() {
	return this.shiftx;
};

filajsHand.prototype.shiftySet = function(shifty) {
	this.shifty = shifty;
	return this;
};

filajsHand.prototype.shiftyGet = function() {
	return this.shifty;
};

filajsHand.prototype.baselineSet = function(x, d) {
	this.baseline = x;
	this.baselineOffset = (d === undefined ? 0 : d);
	return this;
};

filajsHand.prototype.baselineOffsetSet = function(x) {
	this.baselineOffset = x;
	return this;
};

filajsHand.prototype.baselineGet = function() {
	return this.baseline;
};

filajsHand.prototype.baselineOffsetGet = function() {
	return this.baselineOffset;
};

filajsHand.prototype.alignmentSet = function(x) {
	this.alignment = x;
	return this;
};

filajsHand.prototype.alignmentGet = function() {
	return this.alignment;
};

// Η χαρτωσιά μπορεί να είναι «κυκλική», πράγμα που σημαίνει ότι τα φύλλα της
// θα εμφανίζονται σε προκαθορισμένες θέσεις κυκλικά. Οι θέσεις αυτές μπορούν
// καθοριστούν με πολλούς τρόπους.
//
// Έτοιμες θέσεις
// --------------
// Το filajs API παρέχει προκατασκευασμένους οδηγούς για χαρτωσιές δύο, τριών
// και τεσσάρων φύλλων. Αυτές οι θέσεις φέρουν ids της μορφής "filajsCircleNM",
// όπου "N" είναι το πλήθος των προκαθορισμένων θέσεων και "M" είναι η θέση, με
// τη θέση 1 να βρίσκεται στο κάτω μέρος (Νότος) και οι υπόλοιπες θέσεις να
// ακολουθούν πορεία αντίθετη με τη φορά των δεικτών του ωρολογίου. Ο χρήστης
// μπορεί να χρησιμοποιήσει ορισμένες μόνο από τις θέσεις κάποιας ομάδας, π.χ.
// μπορεί να χρησιμοποιήσει μόνο τις θέσεις "filajsCircle41" και "filajsCircle43"
// που είναι ο Νότος και ο Βορράς σε χαρτωσιά τεσσάρων φύλλων.
//
// Ad hoc θέσεις
// -------------
// Πρόκειται για θέσεις που κατασκευάζει ο χρήστης, τις εντάσσει κάπου στο DOM
// και κατόπιν τις χρησιμοποιεί για χωροθέτηση φύλλων οποιασδήποτε χαρτωσιάς.
// Ο συνήθης τρόπο κατασκευής τέτοιου είδους οδηγών είναι να κατασκευαστεί ένα
// πλαίσιο για όλη τη χαρτωσιά και μέσα σε αυτό να τοποθετηθούν «φύλλα», δηλαδή
// div elements διαστάσεων 88x120, με absolute positioning.
//
// Μετά τον προκαθορισμό των θέσεων που θα καταλάβουν τα φύλλα μιας κυκλικής
// χαρτωσιάς, ο χρήστης πρέπει να καθορίσει το baseline και το alignment της
// χαρτωσιάς. Αν τα φύλλα έχουν άλλες διαστάσεις από τις default διαστάσεις
// 88x120, τότε γίνεται αυτόματη προσαρμογή.

filajsHand.prototype.circleSet = function() {
	var i;

	this.circle = [];
	this.rotation = [];

	// Το filajs API παρέχει προκατασκευασμένους οδηγούς για χαρτωσιές δύο,
	// τριών και τεσσάρων φύλλων. Αυτοί οι οδηγοί μπορούν να χρησιμοποιηθούν
	// καλώντας την "circleSet" με παράμετρο το πλήθος των θέσεων.
	//
	// Αν, όμως, θέλουμε π.χ. από τον οδηγό των τριών φύλλων να χρησιμοποιήσουμε
	// τις θέσεις 3 και 2 με αυτή τη σειρά, τότε θα πρέπει να τις καθορίσουμε
	// με διαδοχικές κλήσεις:
	//
	//	x.circleSet('#filajsCircle33');
	//	x.circleSet('#filajsCircle32');
	//
	// είτε με μια κλήση:
	//
	//	x.circleSet('#filajsCircle33', '#filajsCircle32');

	if ((arguments.length === 1) && (typeof arguments[0] === 'number'))
	for (i = 1; i <= arguments[0]; i++)
	this.circle.push($('#filajsCircle' + arguments[0] + '' + i));

	else
	for (i = 0; i < arguments.length; i++)
	this.circle.push(arguments[i]);

	return this;
};

filajsHand.prototype.circlePush = function() {
	var i;

	if (!this.hasOwnProperty('circle'))
	this.circleSet();

	for (i = 0; i < arguments.length; i++)
	this.circle.push(arguments[i]);

	return this;
};

filajsHand.prototype.isCircle = function() {
	return this.circle;
};

filajsHand.prototype.notCircle = function() {
	return !this.isCircle();
};

// Σε κυκλικές χαρτωσιές μπορούμε να εφαρμόσουμε και στροφή στα containers των
// φύλλων της χαρτωσιάς. Η στροφή δίνεται σε μοίρες και μπορεί να είναι θετική
// ή αρνητική, οπότε το φύλλο στρέφεται αριστερόστροφα ή δεξιόστροφα αντίστοιχα.

filajsHand.prototype.rotationPush = function() {
	var i;

	if (!this.hasOwnProperty('rotation'))
	this.rotation = [];

	for (i = 0; i < arguments.length; i++)
	this.rotation.push(arguments[i]);

	return this;
};

// Η μέθοδος "rotationGet" επιστρέφει τη στροφή για το φύλλο της χαρτωσιάς του
// οποίου τον δείκτη περνάμε ως παράμετρο.

filajsHand.prototype.rotationGet = function(n) {
	try {
		return this.rotation[n % this.rotation.length];
	} catch (e) {
		return null;
	}
};

// Η μέθοδος "archSet" δέχεται ένα πλήθος φύλλων και εφαρμόζει στη χαρτωσιά το
// ανάλογο τόξο θέσης με σκοπό να τοποθετηθούν τα φύλλα της χαρτωσιάς κυκλικά
// σε θέσεις που σχηματίζουν τόξο. Τα φύλλα σε τοξοειδείς χαρτωσιές στρέφονται
// εκκινώντας από κάποια αρνητική γωνία ανάλογα με το πλήθος θέσεων του εν λόγω
// τόξου. Η γωνία βαίνει αυξανόμενη κατά βήμα που επίσης προκύπτει από το πλήθος
// θέσεων του εν λόγω τόξου.

filajsHand.prototype.archSet = function(n) {
	var r, dr, i;

	this.circle = [];
	this.rotation = [];

	switch (n) {
	case 2:
		r = -10;
		dr = 20;
		break;
	case 3:
		r = -15;
		dr = 15;
		break;
	case 4:
		r = -18;
		dr = 12;
		break;
	default:
		r = 0;
		dr = 0;
		break;
	}

	for (i = 1; i <= n; i++, r += dr)
	this.
	circlePush($('#filajsArch' + n + '' + i)).
	rotationPush(r);

	return this;
};

filajsHand.prototype.asortiSet = function(asorti) {
	if (asorti === undefined)
	asorti = true;

	this.asorti = asorti;
	return this;
};

filajsHand.prototype.asortiOn = function() {
	return this.asortiSet(true);
};

filajsHand.prototype.asortiOff = function() {
	return this.asortiSet(false);
};

filajsHand.prototype.asortiGet = function() {
	return this.asorti;
};

filajsHand.prototype.isAsorti = function() {
	return this.asorti;
};

filajsHand.prototype.notAsorti = function() {
	return !this.isAsorti();
};

filajsHand.prototype.domCreate = function() {
	if (this.hasOwnProperty('dom'))
	throw filajs.project + ': dom already exists';

	this.dom = $('<div>').
	data('hand', this).
	addClass('filajsHand');

	if (!this.hasOwnProperty('shiftx'))
	this.shiftxSet(filajs.shiftxGet());

	if (!this.hasOwnProperty('shifty'))
	this.shiftySet(filajs.shiftyGet());

	if ((!this.hasOwnProperty('horizontal')) && (!this.hasOwnProperty('vertical')))
	this.horizontalSet(1);

	if (!this.hasOwnProperty('asorti'))
	this.asortiSet();

	return this;
};

filajsHand.prototype.domRefresh = function() {
	var dom, top, bottom, left, right, pos, baseline, alignment, len,
		width, height;

	// Αποσπούμε τυχόν DOM elements φύλλων που ήδη υπάρχουν στη χαρτωσιά
	// και αδειάζουμε το DOM element της χαρτωσιάς.

	this.domEmpty();

	// Κρατάμε σε μεταβλητή το DOM element της χαρτωσιάς, καθώς αυτό θα
	// μας χρειαστεί αρκετές φορές παρακάτω.

	dom = this.domGet();

	// Θα ελέγξουμε αν έχει καθοριστεί baseline ή alignment για την ανά
	// χείρας χαρτωσιά.

	top = 'auto';
	bottom = 'auto';
	left = 'auto';
	right = 'auto';

	pos = false;
	switch (baseline = this.baselineGet()) {
	case 'B':
		pos = true;
		bottom = this.baselineOffsetGet() + 'px';
		break;
	case 'T':
	case 'M':
		pos = true;
		top = this.baselineOffsetGet() + 'px';
		break;
	case 'L':
	case 'C':
		pos = true;
		left = this.baselineOffsetGet() + 'px';
		break;
	case 'R':
		pos = true;
		right = this.baselineOffsetGet() + 'px';
		break;
	}

	// Ελέγχουμε το alignment κρατώντας για αργότερα το είδος του
	// alignment μόνον εφόσον πρόκειται για center (οριζόντια) ή
	// για middle (κάθετα).

	switch (alignment = this.alignmentGet()) {
	case 'L':
		pos = true;
		left = 0;
		alignment = false;
		break;
	case 'R':
		pos = true;
		right = 0;
		alignment = false;
		break;
	case 'T':
		pos = true;
		top = 0;
		alignment = false;
		break;
	case 'B':
		pos = true;
		bottom = 0;
		alignment = false;
		break;
	case 'C':
	case 'M':
		pos = true;
		break;
	}

	// Εφόσον έχει καθοριστεί baseline ή alignment για την ανά χείρας χαρτωσιά,
	// θα πρέπει να τεθεί σε absolute position που μέχρι στιγμής προκύπτει από
	// αυτά τα στοιχεία.

	if (pos)
	dom.css({
		position: 'absolute',
		top: top,
		bottom: bottom,
		left: left,
		right: right,
	});

	len = this.cardsCount();

	// Αν η χαρτωσιά είναι κενή, μηδενίζουμε τις διαστάσεις του DOM element
	// και δεν προβαίνουμε σε περαιτέρω ενέργειες.

	if (!len) {
		dom.
		css({
			width: 0,
			height: 0,
		});
		return this;
	}

	// Αν η χαρτωσιά είναι κυκλική, αναθέτουμε σε εξειδικευμένη μέθοδο την
	// τοποθέτηση των φύλλων.

	if (this.isCircle())
	this.domRefreshCircle(dom, alignment);

	// Αντίστοιχη μέθοδος αναλαμβάνει την τοποθέτηση των φύλλων σε μη κυκλικές
	// χαρτωσιές.

	else
	this.domRefreshLine(dom, len, alignment);

	// Αν η ανά χείρας χαρτωσιά έχει τοποθετηθεί σε πατρικό div με
	// center/middle alignment, τότε μετακινούμε τη χαρτωσιά στο
	// σημείο που πρέπει με βάση το πλάτος ή το ύψος της χαρτωσιάς
	// και του πατρικού div.

	if (alignment)
	switch (alignment) {
	case 'C':
		width = dom.parent().width();
		if (width !== undefined)
		dom.css('left', ((width - dom.width()) / 2) + 'px');
		break;
	case 'M':
		height = dom.parent().height();
		if (height !== undefined)
		dom.css('top', ((height - dom.height()) / 2) + 'px');
		break;
	}

	// Το ίδιο κάνουμε και για το baseline, εφόσον αυτό έχει καθοριστεί
	// στη μέση κατά πλάτος, ή στο κέντρο καθ' ύψος.

	if (baseline)
	switch (baseline) {
	case 'C':
		width = dom.parent().width();
		if (width !== undefined)
		dom.css('left', ((width - dom.width()) / 2) + 'px');
		break;
	case 'M':
		height = dom.parent().height();
		if (height !== undefined)
		dom.css('top', ((height - dom.height()) / 2) + 'px');
		break;
	}

	return this;
};

filajsHand.prototype.domRefreshLine = function(dom, len, alignment) {
	var card, cardDOM, drac, width, height, i, shiftx, shifty;

	// Εντοπίζουμε το πρώτο φύλλο της χαρτωσιάς και το χρησιμοποιούμε ως
	// οδηγό για τα μεγέθη όλων των φύλλων της χαρτωσιάς και κατ' επέκτασιν
	// της ίδιας της χαρτωσιάς.

	card = this.cardGet(0);
	cardDOM = card.domGet();
	width = cardDOM.width();
	height = cardDOM.height();

	// Υπολογίζουμε το πλάτος του απανωτίσματος των φύλλων της χαρτωσιάς
	// ανάλογα με το αν η χαρτωσιά εκτείνεται οριζόντια ή κάθετα.

	if (this.vertical) {
		shiftx = 0;
		shifty = width * this.shiftyGet();
	}
	else {
		shiftx = width * this.shiftxGet();
		shifty = 0;
	}

	// Θέτουμε από τώρα τις τελικές διαστάσεις της χαρτωσιάς και εντάσσουμε
	// σ' αυτήν το πρώτο φύλλο.

	dom.
	css({
		width: (width + ((len - 1) * shiftx)) + 'px',
		height: (height + ((len - 1) * shifty)) + 'px',
	}).
	append($('<div>').
	data('index', 0).
	addClass('filajsHandCard').
	css({
		width: width + 'px',
		height: height + 'px',
	}).
	append(cardDOM));

	// Εντάσσουμε και όλα τα υπόλοιπα φύλλα της χαρτωσιάς στο DOM,
	// διαχωρίζοντας τυχόν ομοιόχρωμα εφόσον αυτά χρήζουν διαχωρισμού.

	for (i = 1; i < len; i++) {
		drac = this.cardGet(i);

		dom.
		append(cardDOM = $('<div>').
		data('index', i).
		addClass('filajsHandCard').
		css({
			left: (shiftx * i) + 'px',
			top: (shifty * i) + 'px',
			width: width + 'px',
			height: height + 'px',
		}).
		append(drac.domGet()));

		this.asortiSep(cardDOM, card, drac, width, height);

		card = drac;
	}

	// Αν η ανά χείρας χαρτωσιά έχει τοποθετηθεί σε πατρικό div με
	// center/middle alignment, τότε μετακινούμε τη χαρτωσιά στο
	// σημείο που πρέπει με βάση το πλάτος ή το ύψος της χαρτωσιάς
	// και του πατρικού div.

	if (alignment)
	switch (alignment) {
	case 'C':
		width = dom.parent().width();
		if (width !== undefined)
		dom.css('left', ((width - dom.width()) / 2) + 'px');
		break;
	case 'M':
		height = dom.parent().height();
		if (height !== undefined)
		dom.css('top', ((height - dom.height()) / 2) + 'px');
		break;
	}

	return this;
};

// Η μέθοδος "asortiSep" ελέγχει αν πρέπει να προστεθεί λίγο χρώμα μεταξύ δύο
// φύλλων προκειμένου να γίνει εμφανής η διαφορά μεταξύ των φυλών εφόσον
// πρόκειται για ομοιόχρωμες φυλές.

filajsHand.prototype.asortiSep = function(dom, card, drac, width, height) {
	//                              |     |     |     |         |
	//                              |     |     |     |         +-- ύψος φύλλου σε px
	//                              |     |     |     +------------ πλάτος φύλλου σε px
	//                              |     |     +------------------ τρέχον φύλλο
	//                              |     +------------------------ προηγούμενο φύλλο
	//                              +------------------------------ DOM element «θήκης» τρέχοντος φύλλου

	if (this.notAsorti())
	return this;

	if (drac.isFaceDown())
	return this;

	if (drac.suitLike(card))
	return this;

	if (drac.colorUnlike(card))
	return this;

	dom.
	addClass('filajsAsorti').
	css('boxShadow', this.vertical ?
	'0px ' + (-height * filajs.asortiRatioV) + 'px 0px ' + filajs.asortiColor :
	(-width * filajs.asortiRatioH) + 'px 0px 0px ' + filajs.asortiColor);

	return this;
};

filajsHand.prototype.domRefreshCircle = function(dom, alignment) {
	var hand = this, filo, filoWidth, filoHeight, thesiDOM,
		thesiWidth, thesiHeight, bazaDOM, scaleX, scaleY, tl, n;

	// Χρησιμοποιούμε το πρώτο φύλλο ως οδηγό τόσο για την χωροθέτηση
	// των φύλλων της χαρτωσιάς, όσο και για το συνολικό μέγεθος της
	// χαρτωσιάς που θα προκύψει.

	filo = this.cardGet(0);
	filoWidth = filo.domGet().width();
	filoHeight = filo.domGet().height();

	// Η πρώτη προκαθορισμένη θέση φύλλων της χαρτωσιάς παίζει σημαντικό
	// ρόλο τόσο στη χωροθέτηση των φύλλων, όσο και στο συνολικό μέγεθος
	// της χαρτωσιάς.

	thesiDOM = this.circle[0];
	thesiWidth = thesiDOM.width();
	thesiHeight = thesiDOM.height();

	// Με βάση το μέγεθος των φύλλων της χαρτωσιάς και το μέγεθος των
	// θέσεων που παρέχονται ως οδηγός χωροθέτησης των φύλλων της χαρτωσιάς,
	// υπολογίζουμε κλίμακα που θα εφαρμοστεί τόσο στο συνολικό μέγεθος της
	// χαρτωσιάς, όσο και στη χωροθέτηση των φύλλων,

	scaleX = filoWidth / thesiWidth;
	scaleY = filoHeight / thesiHeight;
	bazaDOM = thesiDOM.parent();

	dom.css({
		width: (bazaDOM.width() * scaleX) + 'px',
		height: (bazaDOM.height() * scaleY) + 'px',
	});

	// Κρατάμε σε array τις θέσεις των φύλλων σε σχέση με το πατρικό div
	// του οδηγού.

	tl = [];
	for (n = 0; n < this.circle.length; n++)
	tl[n] = this.circle[n].position();

	// Τοποθετούμε τα φύλλα της χαρτωσιάς στο div της χαρτωσιάς, με βάση
	// τα στοιχεία θέσης που καθορίζονται στον οδηγό μετά από scaling.

	this.cardWalk(function(i) {
		var thesi, filoDOM, tl1, tl2, rotation;

		thesi = i % n;
		tl1 = {
			top: tl[thesi].top * scaleY,
			left: tl[thesi].left * scaleX,
		};

		filoDOM = $('<div>').
		data('index', i).
		addClass('filajsHandCard').
		css({
			top: tl1.top + 'px',
			left: tl1.left + 'px',
		}).
		append(this.domGet()).
		appendTo(dom);

		// Αν δεν έχει καθοριστεί στροφή για τη συγκεκριμένη θέση,
		// δεν προβαίνουμε σε περαιτέρω ενέργειες.

		rotation = hand.rotationGet(thesi);
		if (!rotation)
		return;

		// Φαίνεται πως έχει καθοριστεί στροφή για τη συγκεκριμένη
		// θέση, επομένως εφαρμόζομε τη στροφή στο ανά χείρας φύλλο.

		rotation = 'rotate(' + rotation + 'deg)';
		filoDOM.css({
			'transform': rotation,
			'-webkit-transform': rotation,
			'-moz-transform': rotation,
			'-o-transform': rotation,
			'-ms-transform': rotation,
		});

		// Ενδεχομένως να χρειάζεται διόρθωση στα top/left
		// τού φύλλου από πιθανή μετατόπιση λόγω στροφής.

		tl2 = filoDOM.position();

		// Υπολογίζουμε διόρθωση στα top/left από πιθανή
		// μετατόπιση τού φύλλου λόγω στροφής.

		tl1.top -= (tl2.top - tl1.top);
		tl1.left -= (tl2.left - tl1.left);

		// Εφαρμόζουμε τις διορθώσεις ώστε να τοποθετηθεί
		// τελικά το φύλλο στην ακριβή θέση.

		filoDOM.
		css({
			top: tl1.top + 'px',
			left: tl1.left + 'px',
		});
	});

	return this;
};

filajsHand.prototype.domGet = function() {
	return this.dom;
};

filajsHand.prototype.domEmpty = function() {
	var dom;

	dom = this.domGet();
	dom.children('.filajsCard').detach();
	dom.empty();

	return this;
};

filajsHand.prototype.domRemove = function() {
	this.
	cardWalk(function() {
		this.
		domGet().
		detach();
	});
	this.
	domGet().
	remove();

	return this;
};

// Η μέθοδος "cardAnimate" αποσπά από την ανά χείρας χαρτωσιά το φύλλο τού οποίου
// τον index περνάμε ως πρώτη παράμετρο και το τοποθετεί στο τέλος της χαρτωσιάς
// την οποία περνάμε ως δεύτερη παράμετρο. Η τοποθέτηση δεν γίνεται άμεσα, αλλά
// ασύγχρονα, στο τέλος ενός animation κατά το οποίο το container του φύλλου (μαζί
// με το ίδιο το φύλλο) φαίνεται να κινείται από τη θέση που κατείχε στη χαρτωσιά
// αφετηρίας προς τη θέση που πρόκειται να καταλάβει στη χαρτωσιά αφίξεως.
//
// Μπορούμε να περάσουμε και τις παρακάτω options:
//
//	sort		True/false, default false. Δείχνει αν το φύλλο θα προστεθεί
//			στη σωστή θέση αντί του τέλους της χαρτωσιάς, όπου η «σωστή»
//			θέση προκύπτει με την ταξινόμηση της χαρτωσιάς. Σ' αυτή την
//			περίπτωση πρέπει η χαρτωσιά αφίξεως να είναι ήδη ταξινομημένη
//			καθώς σε άλλη περίπτωση θα αλλάξει άρδην η θέση όλων των
//			φύλλων μετά την άφιξη του νέου φύλλου στη χαρτωσιά αφίξεως.
//
//	callback	Callback function που θα κληθεί ως μέθοδος της ανά χείρας
//			χαρτωσιάς με παραμέτρους τη χαρτωσιά αφίξεως και το index
//			του φύλλου σ' αυτή τη χαρτωσιά.
//
//	duration	Ο χρόνος που διάρκειας της κίνησης σε ms (default 400).
//
//	zIndex		Το z-index που θα πάρει το φύλλο κατά την τελευταία φάση
//			της κίνησης. Αν δεν καθοριστεί τίθεται ένα παραπάνω από
//			το z-index του φύλλου που μετακινείται.
//
//	zAfter		Το z-index που θα πάρει το φύλλο μετά την ολοκλήρωση της
//			κίνησης. Αν δεν καθοριστεί, τίθεται ίδιο με αυτό που είχε
//			το φύλλο πριν τη μετακίνησή του.
//
//	width		Το επιθυμητό πλάτος του φύλλου σε pixels μετά το τέλος της
//			κίνησης. Χρησιμοποιείται όταν έχουμε μεταβολή της διάστασης
//			του φύλλου κατά την κίνηση.

filajsHand.prototype.cardAnimate = function(i, handTo, opts) {
	var handFrom = this, countTo, card, drac, w, h, idx, domFrom, domTo, offsetFrom, offsetTo;

	if (opts === undefined)
	opts = {};

	if (!opts.hasOwnProperty('duration'))
	opts.duration = 400;

	// Κρατάμε το φύλλο αφετηρίας, καθώς επίσης και το container του φύλλου
	// στη χαρτωσιά αφετηρίας.

	card = this.cardGet(i);
	domFrom = card.domGet().parent();

	// Προσθέτουμε προσωρινό φύλλο στη χαρτωσιά αφίξεως και επανασχηματίζουμε
	// τη χαρτωσιά προκειμένου να πάρουμε τη θέση του νέου (προσωρινού) φύλλου
	// στο document (συντεταγμένες αφίξεως). Φροντίζουμε το προσωρινό φύλλο να
	// είναι ακριβές αντίγραφο του φύλλου που πρόκειται να μετακινηθεί.

	drac = new filajsCard(card);

	// Αν έχει καθοριστεί πλάτος φύλλου μετά την μετακίνηση, τότε θέτουμε το
	// πλάτος του προσωρινού φύλλου στο επιθυμητό πλάτος.

	if (opts.hasOwnProperty('width'))
	drac.widthSet(opts.width);

	drac.
	domCreate().
	domRefresh();

	handTo.cardPush(drac);

	// Αν έχει καθοριστεί ταξινόμηση κάνουμε επαναταξινόμηση της χαρτωσιάς
	// αφίξεως προκειμένου το προσωρινό φύλλο να τοποθετηθεί στη θέση που θα
	// τοποθετηθεί αργότερα και το πραγματικό φύλλο που πρόκειται να μετακινηθεί.

	if (opts.sort)
	handTo.sort();

	handTo.domRefresh();
	domTo = drac.domGet().parent();
	offsetTo = domTo.offset();
	w = domTo.width();
	h = domTo.height();

	if (!opts.hasOwnProperty('zAfter'))
	opts.zAfter = domTo.css('zIndex');

	if (!opts.hasOwnProperty('zIndex'))
	opts.zIndex = domTo.zIndex() + 1;

	// Καταμετρούμε τα φύλλα στη χαρτωσιά αφίξεως μετά την ένταξη του προσωρινού
	// φύλλου και εντοπίζουμε τη θέση στην οποία έχει μετακινηθεί το προσωρινό
	// φύλλο (στην ίδια θέση θα τοποθετηθεί και το πραγματικό φύλλο όταν τελικά
	// μετακινηθεί). Άμα τω εντοπισμώ, διαγράφουμε τελικά το προσωρινό φύλλο από
	// τη χαρτωσιά αφίξεως.

	countTo = handTo.cardsCount();
	for (idx = 0; idx < countTo; idx++) {
		if (handTo.cardGet(idx) === drac) {
			handTo.cardArray.splice(idx, 1);
			break;
		}
	}

	// ΑΦΟΥ πάρουμε τα στοιχεία θέσης του προσωρινού φύλλου, αφαιρούμε το προσωρινό
	// φύλλο που προσθέσαμε στη χαρτωσιά αφίξεως. Ξεκινάμε τη διαδικασία διαγραφής
	// με τη διαγραφή του σχετικού DOM element.

	domTo.remove();
	handTo.domRefresh();

	// Ακυρώνουμε τυχόν κίνηση του φύλλου και εφόσον το φύλλο είναι μετατοπισμένο,
	// ακυρώνουμε την επαναφορά. ΑΦΟΥ γίνουν αυτά παίρνουμε τα στοιχεία θέσης του
	// φύλλου στο document (συντεταγμένες αφετηρίας).

	domFrom.finish().removeData('rstack');
	offsetFrom = domFrom.offset();

	// Είναι η στιγμή πριν την εκκίνηση της κινήσεως. Μαρκάρουμε το φύλλο ως
	// κινούμενο με το "animation" data.

	domFrom.data('animation', true);

	// Ακυρώνουμε τυχόν διαχωρισμό ομοιόχρωμων όμορων φύλλων και εκκινούμε τη
	// διαδικασία μετακίνησης του φύλλου από τη θέση που κατέχει στη χαρτωσιά
	// αφετηρίας προς τη θέση που πρόκειται να καταλάβει στη χαρτωσιά αφίξεως.

	domFrom.
	removeClass('filajsAsortiHorizontal filajsAsortiVertical').
	css('boxShadow', '').
	animate({
		top: '+=' + (offsetTo.top - offsetFrom.top) + 'px',
		left: '+=' + (offsetTo.left - offsetFrom.left) + 'px',
		width: w + 'px',
		height: h + 'px',
	}, opts.duration, 'easeInOutCubic', function() {
		handTo.handPush(handFrom.extract(i, 1));
		if (opts.sort)
		handTo.sort();

		domFrom.
		css('zIndex', opts.zAfter);

		if (opts.hasOwnProperty('callback')) {
			if (opts.callback)
			opts.callback.call(handFrom, handTo, idx);
		}

		else {
			handFrom.domRefresh();
			handTo.domRefresh();
		}

		// Εφόσον η κίνηση έχει τελειώσει, διαγράφουμε το μαρκάρισμα
		// του φύλλου ως κινουμένου.

		domFrom.removeData('animation');
	});

	// Παράλληλα με την μετακίνηση του φύλλου ανεβάζουμε το z-index προκειμένου
	// το φύλλο να τοποθετηθεί πάνω από τα προηγούμενα φύλλα στη χαρτωσιά
	// αφίξεως.

	setTimeout(function() {
		domFrom.css('zIndex', opts.zIndex);
	}, parseInt(opts.duration / 2.0));

	return this;
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "raise" δέχεται το index ενός φύλλου μιας χαρτωσιάς και «ανεβάζει» το
// φύλλο λίγο προς τα «επάνω». Αν θέλουμε μπορούμε να καθορίσουμε άλλη μετατόπιση
// ως δεύτερη παράμετρο.
//
// Ουσιαστικά μετατοπίζεται το container του φύλλου στη χαρτωσιά. Αν η χαρτωσιά
// είναι κάθετη, τότε η μετατόπιση είναι οριζόντια. Αυτά καθορίζονται από τις
// properties "horizontal" και "vertical" της χαρτωσιάς ως εξής:
//
//	horizontal=1	Η χαρτωσιά απλώνεται ΟΡΙΖΟΝΤΙΑ και η μετατόπιση είναι
//			προς τα ΠΑΝΩ.
//
//	horizontal=-1	Η χαρτωσιά απλώνεται ΟΡΙΖΟΝΤΙΑ και η μετατόπιση είναι
//			προς τα ΚΑΤΩ.
//
//	vertical=1	Η χαρτωσιά απλώνεται ΚΑΘΕΤΑ και η μετατόπιση είναι
//			προς τα ΔΕΞΙΑ.
//
//	vertical=-1	Η χαρτωσιά απλώνεται ΚΑΘΕΤΑ και η μετατόπιση είναι
//			προς τα ΑΡΙΣΤΕΡΑ.

filajsHand.prototype.raise = function(idx, x) {
	var dom, w, rstack;

	try {
		dom = this.cardGet(idx).domGet().parent();

		// Αν το φύλλο βρίσκεται σε κίνηση, δεν προβαίνουμε σε καμία
		// περαιτέρω ενέργεια.

		if (dom.data('animation'))
		return this;

		// Τερματίζουμε τυχόν animation άλλου είδους, προκειμένου να
		// εκκινήσουμε το ανέβασμα.

		dom.finish();
	} catch (e) {
		return this;
	}

	if (x === undefined)
	x = filajs.cardRaise;

	rstack = dom.data('rstack');
	if (!rstack)
	dom.data('rstack', rstack = []);

	w = dom.width() * (x / 88.0);
	rstack.push(w);

	dom.
	animate({
		top: '-=' + (this.horizontal * w) + 'px',
		left: '+=' + (this.vertical * w) + 'px',
	}, 100);

	return this;
};

// Η μέθοδος "lower" δέχεται το index ενός μετατοπισμένου φύλλου μιας χαρτωσιάς και
// επαναφέρει το φύλλο στην αρχική του θέση.

filajsHand.prototype.lower = function(idx) {
	var dom, w;

	try {
		dom = this.cardGet(idx).domGet().parent();
		w = dom.data('rstack').pop();
	} catch (e) {
		return this;
	}

	dom.
	finish().
	animate({
		top: '+=' + (this.horizontal * w) + 'px',
		left: '-=' + (this.vertical * w) + 'px',
	}, 100);

	return this;
};

// Η jQuery μέθοδος "raiseCard" εφαρμόζεται επί "filajsHandCard" DOM elements, δηλαδή επί
// των containers των φύλλων μιας χαρτωσιάς και σκοπό έχει τη μετατόπιση του σχετικού DOM
// element προκειμένου να καταστεί αυτό υποψήφιο για κάποια ενέργεια, ή για άλλο λόγο.

jQuery.fn.raiseCard = function(x) {
	return this.each(function() {
		try {
			$(this).parent().data('hand').raise($(this).data('index'), x);
		} catch (e) {}
	});
};

// Η jQuery μέθοδος "lowerCard" εφαρμόζεται επί "filajsHandCard" DOM elements, δηλαδή επί
// των containers των φύλλων μιας χαρτωσιάς και σκοπό έχει την επαναφορά του σχετικού DOM
// element στη θέση που κατείχε πριν το τελευταίο raise.

jQuery.fn.lowerCard = function() {
	return this.each(function() {
		try {
			$(this).parent().data('hand').lower($(this).data('index'));
		} catch (e) {}
	});
};

$(document).ready(function() {
	var i, dom, j;

	// Δημιουργούμε οδηγούς για κυκλικές χαρτωσιές δύο, τριών και τεσσάρων φύλλων.
	// Οι οδηγοί θα τοποθετηθούν με absolute positioning στην αρχή του document
	// body με visibility hidden, καθώς η μοναδική χρήση αυτών των οδηγών είναι
	// να καθορίζουν τις σχετικές θέσεις των φύλλων μιας κυκλικη χαρτωσιάς. Οι
	// θέσεις αριθμούνται πάντα από το 1, εκτείνονται κυκλικά αντίθετα με τη
	// φορά των δεικτών του ωρολογίου και η θέση 1 βρίσκεται πάντοτε στο κάτω
	// μέρος της χαρτωσιάς (Νότος).

	for (i = 1; i <= 4; i++) {
		dom = $('<div>').
		attr('id', 'filajsCircle' + i).
		addClass('filajsCircleContainer').
		prependTo($(document.body));

		for (j = 1; j <= i; j++)
		dom.append($('<div>').attr('id', 'filajsCircle' + i + '' + j).addClass('filajsCircle'));
	}

	for (i = 2; i <= 4; i++) {
		dom = $('<div>').
		attr('id', 'filajsArch' + i).
		addClass('filajsCircleContainer').
		prependTo($(document.body));

		for (j = 1; j <= i; j++)
		dom.append($('<div>').attr('id', 'filajsArch' + i + '' + j).addClass('filajsCircle'));
	}
});
